﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using Fine.Collections.Const;
using Fine.Collections.Intf;
using Fine.Utils.Functions;
using JetBrains.Annotations;
using NUnit.Framework;



namespace Fine.TestUtils
{
    // ReSharper disable LoopCanBeConvertedToQuery
    [DebuggerStepThrough]
    public static class Assertions
    {
        
        [EditorBrowsable(EditorBrowsableState.Never)]
        [AssertionMethod]
        public static void _true_([AssertionCondition(AssertionConditionType.IS_TRUE)] this bool b)
        {
            Assert.IsTrue(b);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        [AssertionMethod]
        public static void _false_([AssertionCondition(AssertionConditionType.IS_FALSE)] this bool b)
        {
            Assert.IsFalse(b);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        [AssertionMethod]
        public static void _is_null_([AssertionCondition(AssertionConditionType.IS_NULL)] this object obj)
        {
            Assert.IsNull(obj);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        [AssertionMethod]
        public static void _is_not_null_([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object obj)
        {
            Assert.IsNotNull(obj);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _equals_<T>(this T got, T exp)
        {
            Assert.AreEqual(exp, got);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _equals_(this double got, double exp, double limit)
        {
            Assert.AreEqual(exp, got, limit);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _not_equals_<T>(this T got, T exp)
        {
            Assert.AreNotEqual(exp, got);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _not_equals_(this double got, double exp, double limit)
        {
            if (exp-limit <= got && got <= exp+limit)
                Assert.Fail("Expected a double value not equals to {1} +/- {2}  but got {0}.".With(got,exp,limit));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _less_<T>(this T got, T exp)
            where T: IComparable<T>
        {
            if (exp == null)
                throw new ArgumentNullException("exp");

            if (got == null)
                Assert.Fail("Got null when expected a value less than "+exp+".");

            if (got.CompareTo(exp) != -1)
                Assert.Fail("Got "+exp+" when expected a value less than " + exp + ".");
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _greater_<T>(this T got, T exp)
            where T: IComparable<T>
        {
            if (exp == null)
                throw new ArgumentNullException("exp");

            if (got == null)
                Assert.Fail("Got null when expected a value greater than "+exp+".");

            if (got.CompareTo(exp) != +1)
                Assert.Fail("Got "+exp+" when expected a value greater than " + exp + ".");
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _same_<T>(this T got, T exp)
        {
            Assert.AreSame(exp, got);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _not_same_<T>(this T got, T exp)
        {
            Assert.AreNotSame(exp, got);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _array_<T>(this T[] got, params T[] exp)
        {
            Assert.AreEqual(exp, got);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _array_<T>(this Array got, params T[] exp)
        {
            int n1 = got != null ? got.Length : 0,
                n2 = exp != null ? exp.Length : 0;

            if (n1 == 0 && n2 == 0)
                return;

            if (n1 != n2)
                Assert.AreEqual(exp, got);

            // ReSharper disable PossibleNullReferenceException
            int b1 = got.GetLowerBound(0);
            for (int i = 0; i < n1; i++)  // n1 == n2
            {
                object v1 = got.GetValue(b1 + i);
                T v2 = exp[i];
                if (!object.Equals(v1, v2))
                {
                    Assert.AreEqual(exp, got);
                    break;
                }
            }
            // ReSharper restore PossibleNullReferenceException
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _set_<T>([CanBeNull] this ROrderSet<T> got, [CanBeNull] ROrderSet<T> exp)
            where T: IComparable<T>
        {
            var diff = CC1.BiDifference(got, exp);
            if (diff.A.IsEmpty && diff.B.IsEmpty)
                return;

            var buf = new StringBuilder();
            buf.Append("Got a set of ")
               .Append(got.ToHuman())
               .Append(" when expected set of ")
               .Append(exp.ToHuman())
               .Append('.');
            if (diff.B.IsNotEmpty)
                buf.Append(" Leak of items ")
                   .Append(diff.B.ToHuman())
                   .Append('.');
            if (diff.A.IsNotEmpty)
                buf.Append(" Superfluous items ")
                   .Append(diff.A.ToHuman())
                   .Append('.');

            Assert.Fail(buf.ToString());
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _set_<T>([CanBeNull] this ROrderSet<T> got, params T[] exp)
            where T: IComparable<T>
        {
            var expSet = CC1.SortSetOf(exp);
            _set_(got,expSet);
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _contains_<T>(this RSet<T> set, params T[] items)
        {
            if (set == null)
                Assert.Fail("Got null when expected a set containing " + items.ToHuman() + ".");
            if (set.IsEmpty)
                Assert.Fail("Got an empty set when expected a set containing " + items.ToHuman() +".");

            var missed = new List<T>(items.Length);
            foreach (var item in items)
                if (!set.Contains(item))
                    missed.Add(item);

            if (missed.Count > 0)
                Assert.Fail("Got set of {0}\nwhen expected a set containing {1}.\nMissed items: {2}.",
                    set.ToHuman(), items.ToHuman(), missed.ToHuman());
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _contains_<T>(this IEnumerable<T> sequence, params T[] items)
        {
            if (sequence == null)
                Assert.Fail("Got null when expected a sequence containing at least " + items.ToHuman() + ".");

            // T[] a = sequence.ToArray(); // it dowsn't work. why?
            var a = sequence.FetchAll();
            if (a.Count == 0)
                Assert.Fail("Got an empty sequence when expected a sequence containing at least " + items.ToHuman() +".");

            var missed = new List<T>(items.Length);
            foreach (var item in items)
                if (!a.Contains(item))
                    missed.Add(item);

            if (missed.Count > 0)
                Assert.Fail("Got sequence of {0}\nwhen expected a sequence containing at least {1}.\nMissed items: {2}.",
                    a.ToHuman(), items.ToHuman(), missed.ToHuman());
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _is_empty_<T>(this RCollection<T> collection)
        {
            if (!collection.IsEmpty)
                Assert.Fail("Expected an empty collection but got: " + ToHuman(collection));
            if (collection.Count > 0)
                Assert.Fail("Expected an empty collection but got: " + ToHuman(collection));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _is_empty_or_null_<T>([CanBeNull] this RCollection<T> collection)
        {
            if (collection != null && !collection.IsEmpty)
                Assert.Fail("Expected an empty (or null) collection but got: " + ToHuman(collection));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _is_empty_<T>(this IEnumerable<T> sequence)
        {
            var items = FetchAll(sequence);

            if (items.Count > 0)
                Assert.Fail("Expected an empty collection but got: " + ToHuman(items));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _is_empty_or_null_<T>([CanBeNull] this IEnumerable<T> sequence)
        {
            if (sequence == null)
                return;

            var items = FetchAll(sequence);

            if (items.Count > 0)
                Assert.Fail("Expected an empty sequence (or null) but got: " + ToHuman(items));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _is_empty_or_null_([CanBeNull] this string str)
        {
            if (str == null || str.Length == 0)
                return;

            Assert.Fail("Expected an empty or null string but got: \"" + str + "\".");
        }


        private static List<T> FetchAll<T>(this IEnumerable<T> sequence)
        {
            var list = new List<T>();
            foreach (var item in sequence)
                list.Add(item);
            return list;
        }


        private static string ToHuman<T>([CanBeNull] this IEnumerable<T> sequence)
        {
            if (sequence == null)
                return "null";

            var buf = new StringBuilder();
            buf.Append('[');
            var first = true;

            foreach (var item in sequence)
            {
                if (first) first = false;
                else       buf.Append(", ");

                buf.Append(item.ToString());
            }

            buf.Append(']');

            return buf.ToString();
        }


        /*
        public static void _in_<T>(this T value, T min, T max)
            where T: IComparable<T>
        {
            if (value == null)
                Assert.Fail(string.Format("Got null when expected a value form {0}..{1}", min, max));
            if (value.CompareTo(min) == -1)
                Assert.Fail(string.Format("Got too small value {0} when expected a value form {1}..{2}", value, min, max));
            if (value.CompareTo(max) == +1)
                Assert.Fail(string.Format("Got too large value {0} when expected a value form {1}..{2}", value, min, max));
        }
        */


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _in_<T>(this T value, RSet<T> set)
        {
            if (set == null)
                throw new ArgumentNullException("set");
            if (set.IsEmpty)
                throw new ArgumentException("Argument set should not be empty.");

            if (!set.Contains(value))
                Assert.Fail(string.Format("Got {0} when expected one of {1}.", value, set));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _in_<T>(this T value, params T[] exp)
            where T: IComparable<T>
        {
            if (exp == null || exp.Length == 0)
                throw new ArgumentException("At least one possible value should be specified.");

            bool ok;
            if (value == null)
                ok = Array.Exists(exp, x => x == null);
            else
                ok = Array.Exists(exp, x => value.Equals(x));

            if (!ok)
                Assert.Fail(string.Format("Got {0} when expected one of {1}.", value, exp));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void _in_<T>(this T value, IEnumerable<T> items)
            where T: IComparable<T>
        {
            if (items == null)
                throw new ArgumentNullException("items");

            foreach (T item in items)
            {
                if (value == null)
                {
                    if (item == null)
                        return;
                }
                else
                {
                    if (value.Equals(item))
                        return;
                }
            }

            Assert.Fail(string.Format("Got {0} when expected one of {1}.", value, items.ToHuman()));
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        [TerminatesProgram]
        public static void _unreachable_(this object impossibleValue)
        {
            Assert.Fail(string.Format("This code should never be reached but got {0}.", impossibleValue));
        }

    }


}